﻿using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.Linq;
using System.Text;

using log4net;

namespace ITSSP.ActiveDirectorySync
{
    class Program
    {
        private static ILog LOG;

        private static List<Services.ChangeSetEntry> changeSets = new List<Services.ChangeSetEntry>();
        private static Services.UserServiceClient context = new Services.UserServiceClient("BinaryHttpBinding_UserService");

        static void Main(string[] args)
        {
            // configuring log4net
            LOG = LogManager.GetLogger("ITSSP.ActiveDirectorySync");
            log4net.Config.XmlConfigurator.Configure(new System.IO.FileInfo(Properties.Settings.Default.log4netConfig));

            // getting AD password
            System.Console.Write("Password: ");
            string password = System.Console.ReadLine();

            string connectionString = String.Format("LDAP://{0}/{1}", Properties.Settings.Default.ServerName, Properties.Settings.Default.SearchBase);
            LOG.Info("Connecting to: " + connectionString);
            LOG.Info("Connecting as: " + Properties.Settings.Default.UserName);
            
            // get all users from DB
            IEnumerable<Services.User> dbUsers = context.GetADUsers().RootResults;

            // get all users from AD
            DirectoryEntry adBaseEntry = new DirectoryEntry(connectionString, Properties.Settings.Default.UserName, password);
            DirectorySearcher adSearcher = new DirectorySearcher(adBaseEntry, String.Format(Properties.Settings.Default.SearchFilter, DateTime.Now.ToFileTimeUtc()));
            SearchResultCollection adUsers = adSearcher.FindAll();

            LOG.Info("Number of users found in AD: " + adUsers.Count.ToString());
            LOG.Info("Number of users found in DB: " + dbUsers.Count().ToString());

            // process each AD user
            foreach (SearchResult user in adUsers)
            {
                String uid = user.Properties["samaccountname"][0].ToString().ToLowerInvariant();

                Services.User dbMatchingUser = dbUsers.SingleOrDefault(u => u.Login == uid);

                if (dbMatchingUser == null)
                {
                    // new User
                    newUser(user);
                }
                else
                {
                     // User already exists
                    updateUser(dbMatchingUser, user);
                    dbUsers = dbUsers.Where(u => u.Id != dbMatchingUser.Id);
                }
            }

            // DB users without a matching AD user
            foreach (Services.User dbUser in dbUsers)
            {
                deleteUser(dbUser);
            }

            // Print out statistics
            LOG.Info("Run Result");
            LOG.Info("----------------");
            LOG.Info(String.Format("Rows added: {0}", changeSets.Count(cs => cs.Operation == Services.DomainOperation.Insert)));
            LOG.Info(String.Format("Rows updated: {0}", changeSets.Count(cs => cs.Operation == Services.DomainOperation.Update)));
            LOG.Info(String.Format("Rows deleted: {0}", changeSets.Count(cs => cs.Operation == Services.DomainOperation.Delete)));

            // push changes to server
            /*while (changeSets.Count > 10)
            {
                LOG.Info(String.Format("Committing changes .... {0} left", changeSets.Count));
                List<Services.ChangeSetEntry> slice = changeSets.GetRange(0, 10);
                changeSets.RemoveRange(0, 10);
                context.SubmitChanges(slice.ToArray());
            }*/
            foreach (Services.ChangeSetEntry cs in changeSets)
            {
                context.SubmitChanges(new Services.ChangeSetEntry[] { cs });
            }
        }

        /// <summary>
        /// Adds a changeset entry for creating a new user
        /// </summary>
        /// <param name="ldapUser">New user to be added</param>
        static void newUser(SearchResult ldapUser)
        {
            string login        = ldapUser.Properties["samaccountname"][0].ToString().ToLowerInvariant();
            string email        = ldapUser.Properties["mail"][0].ToString();
            string firstName    = ldapUser.Properties["givenName"][0].ToString();
            string lastName     = ldapUser.Properties["sn"][0].ToString();

            Services.ChangeSetEntry cs = new Services.ChangeSetEntry();
            cs.Operation = Services.DomainOperation.Insert;
            cs.Entity = new Services.User { Id = 0, Login = login, EMailAddress = email, FirstName = firstName, LastName = lastName, ActiveDirectoryUser = true, Enabled = true};
            changeSets.Add(cs);

            LOG.Debug("Created user " + login);
        }

        /// <summary>
        /// Add a changeset entry for updating the db user with the properties from AD
        /// </summary>
        /// <param name="dbUser"></param>
        /// <param name="ldapUser"></param>
        static void updateUser(Services.User dbUser, SearchResult ldapUser)
        {
            string login = ldapUser.Properties["samaccountname"][0].ToString().ToLowerInvariant();
            string email = ldapUser.Properties["mail"][0].ToString();
            string firstName = ldapUser.Properties["givenName"][0].ToString();
            string lastName = ldapUser.Properties["sn"][0].ToString();

            Services.ChangeSetEntry cs = new Services.ChangeSetEntry();
            cs.Operation = Services.DomainOperation.Update;
            cs.OriginalEntity = dbUser;
            cs.Entity = new Services.User { Login = login, EMailAddress = email, FirstName = firstName, LastName = lastName, ActiveDirectoryUser = true, Enabled = true };
            changeSets.Add(cs);

            LOG.Debug("Updating user " + login);
        }

        /// <summary>
        /// Add a changeset entry for deleting the db user
        /// </summary>
        /// <param name="dbUser">User to be deleted</param>
        static void deleteUser(Services.User dbUser)
        {
            Services.ChangeSetEntry cs = new Services.ChangeSetEntry();
            cs.Operation = Services.DomainOperation.Delete;
            cs.Entity = dbUser;
            changeSets.Add(cs);

            LOG.Debug("Deleting user " +  dbUser.Login);
        }
    }
}
